package com.ccp.dev.qbdms.expimp.util;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ccp.dev.core.poi.excel.Excel;
import com.ccp.dev.core.poi.excel.editor.IFontEditor;
import com.ccp.dev.core.poi.excel.style.Color;
import com.ccp.dev.core.poi.excel.style.font.BoldWeight;
import com.ccp.dev.core.poi.excel.style.font.Font;
import com.ccp.dev.core.util.AppUtil;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.StringUtil;
import com.ccp.dev.qbdms.expimp.annotation.ExpAnnotation;
import com.ccp.dev.system.model.Dictionary;
import com.ccp.dev.system.service.DictionaryService;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 导出工具类
 */
public abstract class AbstractExpUtil {
    /**
     * 执行导出
     * @param request   HttpServletRequest
     * @param response  HttpServletResponse
     * @param models    需要导出的models
     * @param showParam 需要导出的列
     * @param expStyle  导出的样式
     * @throws IllegalAccessException  IllegalAccessException
     * @throws NoSuchFieldException    NoSuchFieldException
     * @throws IOException             IOException
     * @throws InstantiationException  InstantiationException
     */
    public void executeExport(HttpServletRequest request , HttpServletResponse response , List<? extends Object> models , JSONArray showParam,ExpStyle expStyle) throws IllegalAccessException, NoSuchFieldException, IOException, InstantiationException {
        this.executeExport(request,response,models,showParam,"",expStyle);
    }

    /**
     * 执行导出
     * @param request   HttpServletRequest
     * @param response  HttpServletResponse
     * @param models    需要导出的models
     * @param showParam 需要导出的列
     * @param expStyle  导出的样式
     * @param fileName  自定义文件名称
     * @throws IllegalAccessException  IllegalAccessException
     * @throws NoSuchFieldException    NoSuchFieldException
     * @throws IOException             IOException
     * @throws InstantiationException  InstantiationException
     */
    public void executeExport(HttpServletRequest request , HttpServletResponse response , List<? extends Object> models , JSONArray showParam , String fileName , ExpStyle expStyle) throws IllegalAccessException, NoSuchFieldException, IOException, InstantiationException {
        if(BeanUtils.isEmpty(request)){
            throw new IllegalArgumentException("request 参数不能为空！");
        }
        if(BeanUtils.isEmpty(response)){
            throw new IllegalArgumentException("response 参数不能为空！");
        }
        if(BeanUtils.isEmpty(models)){
            throw new IllegalArgumentException("models 参数不能为空！");
        }
        try (ServletOutputStream os = response.getOutputStream()){
            Excel excel = new Excel();
            String secret = "";
            List<String> fieldOrder = new ArrayList<>();
            Map<String,List<String>> subMap = new LinkedHashMap<>();
            for(int i=0;i<showParam.size();i++){
                JSONObject cjo = showParam.getJSONObject(i);
                if(cjo.getBoolean("isMain")){
                    String fields = cjo.getString("fields");
                    fieldOrder = Arrays.asList(fields.split(","));
                    secret = cjo.getString("secret");
                }else{
                    subMap.put(cjo.getString("modelName"),Arrays.asList(cjo.getString("fields").split(",")));
                }
            }
            //设置密级
            excel.cell(0,0).value(secret).font(new IFontEditor() {
                // 设置字体
                @Override
                public void updateFont(Font font) {
                    font.boldweight(BoldWeight.BOLD);
                }
            }).bgColor(Color.GREY_25_PERCENT).width(DEF_WIDTH);

            if(expStyle.equals(ExpStyle.SINGLE_SHEET)){
                //组装数据对象
                String retName = assembleModels(models,fieldOrder,subMap,excel);
                if(StringUtil.isEmpty(fileName)){
                    fileName = retName;
                }
            }else{
                String retName = assembleModelsManySheet(models,fieldOrder,subMap,excel,null,0);
                if(StringUtil.isEmpty(fileName)){
                    fileName = retName;
                }
            }
            fileName += "("+secret+")";
            response.setContentType("application/vnd.ms-excel");
            String info = getBrowserInfo(request);
            if(info.contains("Firefox")){
                response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1) + ".xls");
            }else{
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + ".xls");
            }
            excel.getWorkBook().write(os);
            os.flush();
        }
    }

    private int rowIndex = 1;

    private int columnIndex = 0;

    public static final int DEF_WIDTH = 3500;

    /**
     * 组装对象
     * @param models 参数对象
     * @param fieldOrder 需要导出的字段
     * @param subMap 子表
     * @throws IllegalAccessException 抛出异常
     */
    private String assembleModels(List<? extends Object> models, List<String> fieldOrder,Map<String,List<String>> subMap,Excel excel) throws IllegalAccessException, NoSuchFieldException, InstantiationException {
        IFontEditor fontEditor = font -> font.boldweight(BoldWeight.BOLD);
        String fileName = getFileName(models.get(0));
        for(int i=0;i<models.size();i++) {
            Class<?> currentClass = models.get(i).getClass();
            //处理字段
            for(int j=0;j<fieldOrder.size();j++){
                Field field = currentClass.getDeclaredField(fieldOrder.get(j));
                ExpAnnotation fieldAnnotation = field.getAnnotation(ExpAnnotation.class);
                String value = getValue(fieldAnnotation,field,models.get(i));
                if(columnIndex > 0){
                    if(i == 0){
                        if(j == fieldOrder.size()-1){
                            excel.cell(this.rowIndex,this.columnIndex+j+1).value("(子表："+fileName+")").font(fontEditor).bgColor(Color.GREY_25_PERCENT);
                        }
                        excel.cell(this.rowIndex,this.columnIndex + j).value(fieldAnnotation.description()).font(fontEditor).bgColor(Color.GREY_25_PERCENT);
                        excel.cell(this.rowIndex+1,this.columnIndex + j).value(value);
                    }else{
                        excel.cell(this.rowIndex,this.columnIndex + j).value(value);
                    }
                }else{
                    if(i == 0){
                        excel.cell(this.rowIndex,this.columnIndex + j).value(fieldAnnotation.description()).font(fontEditor).bgColor(Color.GREY_25_PERCENT).width(fieldAnnotation.width());
                    }
                    excel.cell(this.rowIndex+1,this.columnIndex + j).value(value);
                }
            }
            if(!subMap.isEmpty()){
                Set<String> set = subMap.keySet();
                int subNum = 0;
                for(Iterator<String> it=set.iterator();it.hasNext();){
                    this.columnIndex = 0;
                    String key = it.next();
                    List<String> subFieldOrder = subMap.get(key);
                    List<Object> subDataList = getSubDataList(models.get(i),key);
                    this.columnIndex++;
                    this.rowIndex++;
                    if(subNum==0){
                        this.rowIndex++;
                    }
                    subNum++;
                    assembleModels(subDataList,subFieldOrder,new HashMap<>(),excel);
                    this.rowIndex++;
                }
                this.columnIndex = 0;
            }else{
                if(i != models.size()-1){
                    this.rowIndex++;
                }
            }
        }
        return fileName;
    }

    /**
     * 组装对象多sheet
     * @param models 参数对象
     * @param fieldOrder 需要导出的字段
     * @param subMap 子表
     * @throws IllegalAccessException 抛出异常
     */
    private String assembleModelsManySheet(List<? extends Object> models, List<String> fieldOrder,Map<String,List<String>> subMap,Excel excel , Map<String,SheetInfo> map , int pid) throws IllegalAccessException, NoSuchFieldException, InstantiationException {
        IFontEditor fontEditor = font -> font.boldweight(BoldWeight.BOLD);
        String fileName = getFileName(models.get(0));
        if(null == map){
            map = new HashMap<>();
        }
        SheetInfo sheetInfo = getSheetInfo(fileName,map);
        excel.setWorkingSheet(sheetInfo.getSheetIndex());
        excel.sheet().sheetName(fileName);
        List<String> keys = new ArrayList<>();
        List<String> vals = new ArrayList<>();
        int[] widths = new int[fieldOrder.size()+1];
        if(pid == 0){
            keys.add("id");
            widths[0] = DEF_WIDTH;
        }else{
            keys.add("refId");
            widths[0] = DEF_WIDTH;
        }
        for(int i=0;i<models.size();i++) {
            Class<?> currentClass = models.get(i).getClass();
            int rowNum = sheetInfo.getRowNum();
            int num = i+1;
            vals.clear();;
            if(pid == 0){
                vals.add(String.valueOf(num));
            }else{
                vals.add(String.valueOf(pid));
            }
            //处理字段
            for(int j=0;j<fieldOrder.size();j++){
                Field field = AuxiliaryUtil.getFieldByName(currentClass,fieldOrder.get(j));
                ExpAnnotation fieldAnnotation = field.getAnnotation(ExpAnnotation.class);
                keys.add(fieldAnnotation.description());
                widths[j+1] = fieldAnnotation.width();
                String value = getValue(fieldAnnotation,field,models.get(i));
                vals.add(value);
            }
            if(i == 0){
                if(pid != 0){
                    boolean sign = false;
                    for(int a = 1 ; a < vals.size() ; a++){
                        if(StringUtil.isNotEmpty(vals.get(a))){
                            sign = true;
                        }
                    }
                    if(rowNum >= 1){
                        if(sign){
                            //有效数据
                            excel.row(rowNum,0).value(vals.toArray());
                            sheetInfo.setRowNum(rowNum+1);
                        }
                    }else{
                        excel.row(rowNum,0).value(keys.toArray()).font(fontEditor).bgColor(Color.GREY_25_PERCENT).width(widths);
                        if(sign){
                            //有效数据
                            excel.row(rowNum+1,0).value(vals.toArray());
                            sheetInfo.setRowNum(rowNum+2);
                        }else{
                            sheetInfo.setRowNum(rowNum+1);
                        }
                    }
                }else{
                    excel.row(rowNum,0).value(keys.toArray()).font(fontEditor).bgColor(Color.GREY_25_PERCENT).width(widths);
                    excel.row(rowNum+1,0).value(vals.toArray());
                    sheetInfo.setRowNum(rowNum+2);
                }
            }else{
                excel.row(rowNum,0).value(vals.toArray());
                sheetInfo.setRowNum(rowNum+1);
            }
            if(!subMap.isEmpty()){
                Set<String> set = subMap.keySet();
                for(Iterator<String> it=set.iterator();it.hasNext();){
                    String key = it.next();
                    List<String> subFieldOrder = subMap.get(key);
                    List<Object> subDataList = getSubDataList(models.get(i),key);
                    assembleModelsManySheet(subDataList,subFieldOrder,new HashMap<>(),excel,map,num);
                }
                excel.setWorkingSheet(sheetInfo.getSheetIndex());
            }
        }
        map.put(fileName,sheetInfo);
        return fileName;
    }

    /**
     * @param o 当前实例
     * @param key 子表key
     * @return 子表list
     * @throws IllegalAccessException IllegalAccessException
     * @throws InstantiationException InstantiationException
     * @throws NoSuchFieldException NoSuchFieldException
     */
    private List<Object> getSubDataList(Object o , String key) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        Field field = o.getClass().getDeclaredField(key);
        field.setAccessible(true);
        Object value = field.get(o);
        Class<?> subClass =  field.getType();
        List<Object> subDataList = new ArrayList<>();
        if(subClass.isArray()){
            subClass = subClass.getComponentType();
            if(null == value || ((Object[])value).length<1){
                subDataList.add(subClass.newInstance());
            }else{
                subDataList.addAll(Arrays.asList((Object[])value));
            }
        }else if(Collection.class.isAssignableFrom(subClass)){
            Type type = field.getGenericType();
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                subClass = (Class<?>)pt.getActualTypeArguments()[0];
            }
            if(null == value || ((ArrayList)value).size()<1){
                subDataList.add(subClass.newInstance());
            }else{
                subDataList.addAll((ArrayList)value);
            }
        }else{
            if(null == value){
                subDataList.add(subClass.newInstance());
            }else{
                subDataList.add(value);
            }
        }
        return subDataList;
    }

    /**
     * 获取当前的sheetinfo
     * @param fileName sheet名
     * @param map   参数
     * @return SheetInfo
     */
    private SheetInfo getSheetInfo(String fileName , Map<String,SheetInfo> map){
        SheetInfo sheetInfo = null;
        if(BeanUtils.isEmpty(map)){
            sheetInfo = new SheetInfo();
            sheetInfo.setSheetIndex(0);
            sheetInfo.setRowNum(1);
            map.put(fileName,sheetInfo);
        }else{
            if(!map.containsKey(fileName)){
                int index = 0;
                for (SheetInfo value : map.values()) {
                    int sheetIndex = value.getSheetIndex();
                    if(index < sheetIndex){
                        index = sheetIndex;
                    }
                }
                sheetInfo = new SheetInfo();
                sheetInfo.setSheetIndex(++index);
                sheetInfo.setRowNum(0);
            }else{
                sheetInfo = map.get(fileName);
            }
        }
        return sheetInfo;
    }

    /**
     * 对象
     * @param o 引用
     * @return fileName
     */
    private String getFileName(Object o){
        if(BeanUtils.isEmpty(o)){
            return null;
        }
        //类
        Class<?> currentClass = o.getClass();
        //类注解
        ExpAnnotation currentAnnotation = currentClass.getAnnotation(ExpAnnotation.class);
        String fileName = "";
        if(BeanUtils.isNotEmpty(currentAnnotation)){
            fileName = currentAnnotation.description();
        }
        if(StringUtil.isEmpty(fileName)){
            fileName = currentClass.getSimpleName();
        }
        return fileName;
    }

    /**
     * 获取到值
     * @param fieldAnnotation  注解
     * @param field 字段
     * @param o 对象
     * @return value
     * @throws IllegalAccessException IllegalAccessException
     */
    private String getValue(ExpAnnotation fieldAnnotation , Field field , Object o) throws IllegalAccessException {
        String jsonParse = fieldAnnotation.jsonParse();
        String formt = fieldAnnotation.format();
        String dicKey = fieldAnnotation.dicKey();
        field.setAccessible(true);
        String value = null == field.get(o) ? "" : field.get(o).toString();
        //增加自定义验证
        String newVal = customAnalysis(field.getName(),value);
        if(BeanUtils.isNotEmpty(newVal) && !value.equals(newVal)){
            value = newVal;
        }else{
            if(Date.class.isAssignableFrom(field.getType()) && StringUtil.isNotEmpty(value)){
                value = new SimpleDateFormat(formt).format(new Date(value));
            }else if(StringUtil.isNotEmpty(jsonParse)){
                JSONObject curJO = JSONObject.parseObject(jsonParse);
                if(StringUtil.isNotEmpty(value)){
                    value = curJO.getString(value);
                }
            }else if(StringUtil.isNotEmpty(dicKey)){
                DictionaryService dictionaryService = AppUtil.getBean(DictionaryService.class);
                List<com.ccp.dev.system.model.Dictionary> dictionaryList = dictionaryService.getByNodeKey(dicKey);
                if(!dictionaryList.isEmpty()){
                    String finalValue = value;
                    List<com.ccp.dev.system.model.Dictionary> oneDic = dictionaryList.stream().filter(a->a.getItemValue().equals(finalValue)).collect(Collectors.toList());
                    if(!oneDic.isEmpty()){
                        Dictionary dictionary = oneDic.get(0);
                        value = dictionary.getItemName();
                    }
                }
            }
        }
        return value;
    }

    /**
     *  可以自定义验证
     * @param fieldName  字段名称
     * @param fieldValue 导入进来的value值
     * @return true 验证通过  false 验证失败
     */
    public abstract String customAnalysis(String fieldName , String fieldValue);

    /**
     * 获取客户端浏览器版本，可能判断不严谨，根据实际情况进行扩充和完善
     * @param request request
     * @return 浏览器
     */
    private String getBrowserInfo(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        String browserInfo="";
        if (userAgent.contains("Edge")){
            browserInfo=userAgent.substring(userAgent.indexOf("Edge")).split(" ")[0];
        } else if (userAgent.contains("MSIE")){
            String subString=userAgent.substring(userAgent.indexOf("MSIE")).split(";")[0];
            browserInfo=subString.split(" ")[0].replace("MSIE", "IE")+"/"+subString.split(" ")[1];
        } else if (userAgent.contains("Safari") && userAgent.contains("Version")){
            browserInfo=(userAgent.substring(userAgent.indexOf("Safari")).split(" ")[0]).split("/")[0]
                    + "/" +(userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];
        } else if ( userAgent.contains("OPR") || userAgent.contains("Opera")){
            if(userAgent.contains("Opera")){
                browserInfo=(userAgent.substring(userAgent.indexOf("Opera")).split(" ")[0]).split("/")[0]
                        +"/"+(userAgent.substring(userAgent.indexOf("Version")).split(" ")[0]).split("/")[1];
            }else if(userAgent.contains("OPR")){
                browserInfo=(userAgent.substring(userAgent.indexOf("OPR")).split(" ")[0])
                        .replace("OPR", "Opera");
            }
        } else if (userAgent.contains("Chrome")){
            browserInfo=(userAgent.substring(userAgent.indexOf("Chrome")).split(" ")[0]);
        }else if (userAgent.contains("Firefox")){
            browserInfo=userAgent.substring(userAgent.indexOf("Firefox")).split(" ")[0];
        } else{
            browserInfo = "other";
        }
        return browserInfo;
    }

    class SheetInfo{
        /**
         * 行数
         */
        int rowNum;
        /**
         * 当前sheet的下标
         */
        int sheetIndex;

        public int getRowNum() {
            return rowNum;
        }

        public void setRowNum(int rowNum) {
            this.rowNum = rowNum;
        }

        public int getSheetIndex() {
            return sheetIndex;
        }

        public void setSheetIndex(int sheetIndex) {
            this.sheetIndex = sheetIndex;
        }
    }

    /**
     * 导出样式枚举
     */
    public enum ExpStyle{
        /**
         * 单sheet
         */
        SINGLE_SHEET,
        /**
         * 多sheet
         */
        MANY_SHEET,
    }
}
